package com.alibaba.shopping_seckill_service.service;

import cn.hutool.bloomfilter.BitMapBloomFilter;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.shopping_common.pojo.CartGoods;
import com.alibaba.shopping_common.pojo.Orders;
import com.alibaba.shopping_common.pojo.SeckillGoods;
import com.alibaba.shopping_common.result.BusException;
import com.alibaba.shopping_common.result.CodeEnum;
import com.alibaba.shopping_common.service.GoodsService;
import com.alibaba.shopping_common.service.SeckillService;
import com.alibaba.shopping_seckill_service.mapper.seckillGoodsMapper;
import com.alibaba.shopping_seckill_service.redis.RedissonLock;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName SeckillServiImpl
 * @Description TODO
 * @Author Leo
 * @Date 2024/12/20 15:19
 */
@DubboService
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private seckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private BitMapBloomFilter bitMapBloomFilter;

    @Autowired
    private RedissonLock redissonLock;

    @Scheduled(cron = "0 * * * * *")
    public void refreshRedis(){
        System.out.println("从MySQL开始同步秒杀商品到Redis中......");
        List<SeckillGoods> seckillGoods1 = redisTemplate.boundHashOps("seckillGoods").values();
        for (SeckillGoods seckillGoods : seckillGoods1) {
            // 根据goodsID查询出数据中的数据
            QueryWrapper<SeckillGoods> wrapper = new QueryWrapper<>();
            wrapper.eq("goodsId",seckillGoods.getGoodsId());
            SeckillGoods seckillGoods2 = seckillGoodsMapper.selectOne(wrapper);
            // 修改数据库中秒杀商品的剩余库存
            seckillGoods2.setStockCount(seckillGoods.getStockCount());
            seckillGoodsMapper.updateById(seckillGoods2);
        }
        // 构造查询秒杀商品的条件
        QueryWrapper<SeckillGoods> wrapper = new QueryWrapper<>();
        Date date = new Date();
        String now_date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
        // 当前时间大于开始时间
        wrapper.le("startTime",now_date)
                // 小于结束时间
                .ge("endTime",now_date)
                // 库存大于0
                .gt("stockCount",0);
        // 执行查询
        List<SeckillGoods> seckillGoods = seckillGoodsMapper.selectList(wrapper);
        // 删除Redis中之前的秒杀商品
        redisTemplate.delete("seckillGoods");
        for (SeckillGoods seckillGood : seckillGoods) {
            // 向Redis中保存现在正在秒杀的商品
            redisTemplate.boundHashOps("seckillGoods").put(seckillGood.getGoodsId(),seckillGood);
            // 将商品ID同步到布隆过滤器中
            bitMapBloomFilter.add(seckillGood.getGoodsId().toString());
        }
    }

    @Override
    public Page<SeckillGoods> findPageByRedis(int page, int size) {
        // 从Redis中查询所有的秒杀商品
        List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();
        // Redis索引是从0开始的
        int startIndex = (page -1)*size;
        int endIndex = 0;
        // 结束索引
        if (seckillGoodsList != null) {
            endIndex = Math.min(startIndex + size, seckillGoodsList.size());
        }
        // 截取列表
        List<SeckillGoods> seckillGoods = seckillGoodsList.subList(startIndex, endIndex);
        // 封装页面Page对象
        Page<SeckillGoods> page1 = new Page<>();
        page1.setTotal(seckillGoodsList.size()); // 设置总条数
        page1.setCurrent(page); // 设置当前页
        page1.setSize(size); // 每页条数
        page1.setRecords(seckillGoods); // 设置结果集
        return page1;
    }

    /**
     * 根据ID查询秒杀商品
     * @param goodsId 秒杀商品对应的商品Id
     * @return
     */
    @Override
    public SeckillGoods findSeckillGoodsByRedis(Long goodsId) {
        // 布隆过滤器判断秒杀商品是否存在，如果不存在， 直接返回空
        // if (!bitMapBloomFilter.contains(goodsId.toString())){
        //     System.out.println("布隆过滤器判断商品 "+goodsId+" 不存在！");
        //     return null;
        // }
        // 1. 从Redis中查询秒杀商品
        SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps("seckillGoods").get(goodsId);
        // 2. 如果查到商品，直接返回
        if (seckillGoods != null){
            System.out.println("从redis中查询秒杀商品成功");
            return seckillGoods;
        }else {
            return null;
        }
    }

    // 使用代码锁来控制超卖现象
    @Override
    public synchronized Orders createOrder(Orders orders) {
        // 获取锁
        String lockKey = orders.getCartGoods().get(0).getOrderId().toString();
        if (redissonLock.lock(lockKey, 10000)){
            try{
                // 生成订单对象
                orders.setId(IdWorker.getIdStr()); // 使用雪花算法生成订单ID，保证唯一性
                orders.setStatus(1); // 订单状态设置成未付款
                orders.setCreateTime(new Date());
                // 订单过期时间
                orders.setExpire(new Date(new Date().getTime() + 1000*60*5));
                // 计算商品价格
                CartGoods cartGoods = orders.getCartGoods().get(0);
                Integer num = cartGoods.getNum();
                BigDecimal price = cartGoods.getPrice();
                // 订单总价格
                BigDecimal sum = price.multiply(BigDecimal.valueOf(num));
                orders.setPayment(sum);
                // 2. 扣减秒杀商品库存
                // 查询秒杀商品
                SeckillGoods seckillGoods = findSeckillGoodsByRedis(cartGoods.getGoodId());
                // 查询当前库存
                Integer stockCount = seckillGoods.getStockCount();
                if (stockCount<=0){
                    // 抛出库存不足的异常
                    throw new BusException(CodeEnum.OOT_Of_STOCK_ERROR);
                }
                // 第一次同步的时候岂不是可能存在空指针，因为第一次遍历时Redis中肯定没有数据
                // 扣减库存
                seckillGoods.setStockCount(stockCount - cartGoods.getNum());
                // 将最新的数据更新到Redis中
                redisTemplate.setKeySerializer(new StringRedisSerializer());
                // 键：订单ID，值：orders对象 1分钟过期
                redisTemplate.opsForValue().set(orders.getId(),orders,1, TimeUnit.MINUTES);
                /*
                  设置订单副本数据，用于存储订单数据，副本过期时间长于主本数据
                  当主本数据过期后，对应的监听方法就会查询副本的数据来进行更新
                 */
                //
                redisTemplate.opsForValue().set(orders.getId()+"_copy",orders,3, TimeUnit.MINUTES);
                return orders;
            }finally {
                redissonLock.unlock(lockKey);
            }
        }else {
            return null;
        }
    }

    @Override
    public Orders findOrders(String id) {
        // 根据订单ID从Redis中查询订单详情数据
        return (Orders) redisTemplate.opsForValue().get(id);
    }

    /**
     * 支付秒杀订单
     * @param orderId
     * @return
     */
    @Override
    public Orders pay(String orderId) {
        // 1.查询订单，设置数据
        Orders orders = (Orders) redisTemplate.opsForValue().get(orderId);
        if (orders == null){
            throw new BusException(CodeEnum.ORDER_EXPIRED_ERROR);
        }
        orders.setStatus(2);
        orders.setPaymentTime(new Date());
        orders.setPaymentType(2); // 支付宝支付

        // 2.从Redis中删除数据
        redisTemplate.delete(orderId);
        redisTemplate.delete(orderId+"copy_");
        // 3.返回数据
        return orders;
    }

    /**
     * 向Redis中添加秒杀商品数据
     * @param seckillGoods
     */
    @Override
    public void addRedisSeckillGoods(SeckillGoods seckillGoods) {
        redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getGoodsId(),seckillGoods);
    }

    // 监控该方法调用量
    // 当触发降级策略时就会直接调用mySqlBlockHandler处理器
    @SentinelResource(value = "findSeckillGoodsByMySql",blockHandler = "mySqlBlockHandler")
    @Override
    public SeckillGoods findSeckillGoodsByMySql(Long goodsId) {
        // 3.如果没有查到商品，从数据库查询秒杀商品
        QueryWrapper<SeckillGoods> wrapper = new QueryWrapper<>();
        wrapper.eq("goodsId",goodsId);
        SeckillGoods seckillGoodsMsql = seckillGoodsMapper.selectOne(wrapper);
        System.out.println("从MySQL中查询秒杀商品成功，"+seckillGoodsMsql);
        // 4. 如果商品不在秒杀状态则直接返回null
        Date now = new Date();
        if (seckillGoodsMsql == null
                || now.after(seckillGoodsMsql.getEndTime())
                || now.before(seckillGoodsMsql.getStartTime())
                || seckillGoodsMsql.getStockCount() <=0
        ){
            return null;
        }
        // 5. 如果商品在秒杀状态则直接返回，并保存到Redis中
        addRedisSeckillGoods(seckillGoodsMsql);
        return seckillGoodsMsql;
    }

    /**
     * 服务降级处理策略
     * @param id
     * @param e
     * @return
     */
    public SeckillGoods mySqlBlockHandler(Long id, BlockException e){
        System.out.println("服务降级方法");
        return null;
    }
}




